/******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.corext.refactoring.nls; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import com.ibm.icu.text.Collator; import org.eclipse.text.edits.DeleteEdit; import org.eclipse.text.edits.InsertEdit; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.TextUtilities; import org.eclipse.ltk.core.refactoring.TextChange; import org.eclipse.jdt.internal.corext.refactoring.changes.TextChangeCompatibility; import org.eclipse.jdt.internal.corext.util.Messages; import org.eclipse.jdt.internal.corext.util.Strings; import org.eclipse.jdt.internal.ui.propertiesfileeditor.PropertiesFileEscapes; import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels; public class PropertyFileDocumentModel { private List<KeyValuePairModell> fKeyValuePairs; private String fLineDelimiter; public PropertyFileDocumentModel(IDocument document) { parsePropertyDocument(document); fLineDelimiter= TextUtilities.getDefaultLineDelimiter(document); } /** * @return the line delimiter used by the document described by this model */ public String getLineDelimiter() { return fLineDelimiter; } /** * Return the key value pair in this model with the key <code>key</code> * * @param key the key of the pair * @return the pair with the key or <b>null</b> if no such pair. */ public KeyValuePair getKeyValuePair(String key) { for (int i= 0; i < fKeyValuePairs.size(); i++) { KeyValuePairModell keyValuePair = fKeyValuePairs.get(i); if (keyValuePair.getKey().equals(key)) { return keyValuePair; } } return null; } private InsertEdit insert(KeyValuePair keyValuePair) { KeyValuePairModell keyValuePairModell = new KeyValuePairModell(keyValuePair); int index = findInsertPosition(keyValuePairModell); KeyValuePairModell insertHere = fKeyValuePairs.get(index); int offset = insertHere.fOffset; String extra= ""; //$NON-NLS-1$ if (insertHere instanceof LastKeyValuePair && ((LastKeyValuePair)insertHere).needsNewLine()) { extra= fLineDelimiter; ((LastKeyValuePair)insertHere).resetNeedsNewLine(); offset-= insertHere.fLeadingWhiteSpaces; } else if (index > 0) { String beforeKey= fKeyValuePairs.get(index - 1).fKey; String afterKey= insertHere.fKey; String key= keyValuePair.fKey; int distBefore= NLSUtil.invertDistance(key, beforeKey); int distAfter= NLSUtil.invertDistance(key, afterKey); if (distBefore > distAfter) { offset-= insertHere.fLeadingWhiteSpaces; } else if (distBefore == distAfter && Collator.getInstance().compare(beforeKey, afterKey) < 0) { offset-= insertHere.fLeadingWhiteSpaces; } else { //insert it before afterKey -> move the leading white spaces to the inserted pair keyValuePairModell.fLeadingWhiteSpaces= insertHere.fLeadingWhiteSpaces; insertHere.fLeadingWhiteSpaces= 0; } } keyValuePairModell.fOffset= offset; fKeyValuePairs.add(index, keyValuePairModell); return new InsertEdit(offset, extra + keyValuePairModell.getKeyValueText()); } /** * Inserts the given key value pairs into this model at appropriate * positions. Records all required text changes in the given change * * @param keyValuePairs the key value pairs to insert * @param change the change to use to record text changes */ public void insert(KeyValuePair[] keyValuePairs, TextChange change) { ArrayList<KeyValuePair> sorted= new ArrayList<KeyValuePair>(Arrays.asList(keyValuePairs)); Collections.sort(sorted, new Comparator<KeyValuePair>() { public int compare(KeyValuePair p1, KeyValuePair p2) { return Collator.getInstance().compare(p1.fKey, p2.fKey); } }); for (int i = 0; i < sorted.size(); i++) { KeyValuePair curr= sorted.get(i); InsertEdit insertEdit= insert(curr); String message= Messages.format(NLSMessages.NLSPropertyFileModifier_add_entry, BasicElementLabels.getJavaElementName(curr.getKey())); TextChangeCompatibility.addTextEdit(change, message, insertEdit); } } public DeleteEdit remove(String key) { for (Iterator<KeyValuePairModell> iter = fKeyValuePairs.iterator(); iter.hasNext();) { KeyValuePairModell keyValuePair = iter.next(); if (keyValuePair.fKey.equals(key)) { return new DeleteEdit(keyValuePair.fOffset, keyValuePair.getLength()); } } return null; } public ReplaceEdit replace(KeyValuePair toReplace, KeyValuePair replaceWith) { for (Iterator<KeyValuePairModell> iter = fKeyValuePairs.iterator(); iter.hasNext();) { KeyValuePairModell keyValuePair = iter.next(); if (keyValuePair.fKey.equals(toReplace.getKey())) { String newText= new KeyValuePairModell(replaceWith).getKeyValueText(); return new ReplaceEdit(keyValuePair.fOffset, keyValuePair.getLength(), newText); } } return null; } private int findInsertPosition(KeyValuePairModell keyValuePair) { ArrayList<String> keys= new ArrayList<String>(); for (int i= 0; i < fKeyValuePairs.size(); i++) { KeyValuePairModell element = fKeyValuePairs.get(i); if (! (element instanceof LastKeyValuePair)) keys.add(element.getKey()); } int insertIndex= NLSUtil.getInsertionPosition(keyValuePair.getKey(), keys); if (insertIndex < fKeyValuePairs.size() - 1) { insertIndex++; } return insertIndex; } private void parsePropertyDocument(IDocument document) { fKeyValuePairs = new ArrayList<KeyValuePairModell>(); SimpleLineReader reader = new SimpleLineReader(document); int offset = 0; String line = reader.readLine(); int leadingWhiteSpaces = 0; while (line != null) { if (!SimpleLineReader.isCommentOrWhiteSpace(line)) { int idx = getIndexOfSeparationCharacter(line); if (idx != -1) { String key= line.substring(0, idx).trim(); String value= Strings.trimLeadingTabsAndSpaces(line.substring(idx + 1)); fKeyValuePairs.add(new KeyValuePairModell(key, value, offset, leadingWhiteSpaces)); leadingWhiteSpaces = 0; } } else { leadingWhiteSpaces += line.length(); } offset += line.length(); line = reader.readLine(); } int lastLine= document.getNumberOfLines() - 1; boolean needsNewLine= false; try { needsNewLine= !(document.getLineLength(lastLine) == 0); } catch (BadLocationException ignore) { // treat last line having no new line } LastKeyValuePair lastKeyValuePair = new LastKeyValuePair(offset, needsNewLine); fKeyValuePairs.add(lastKeyValuePair); } private int getIndexOfSeparationCharacter(String line) { int minIndex = -1; int indexOfEven = line.indexOf('='); int indexOfColumn = line.indexOf(':'); int indexOfBlank = line.indexOf(' '); if ((indexOfEven != -1) && (indexOfColumn != -1)) { minIndex = Math.min(indexOfEven, indexOfColumn); } else { minIndex = Math.max(indexOfEven, indexOfColumn); } if ((minIndex == -1) && (indexOfBlank != -1)) { minIndex= indexOfBlank; } return minIndex; } public static String escape(String s, boolean escapeCommentCharsAndLeadingWhitespaces) { StringBuffer sb= new StringBuffer(s.length()); int length= s.length(); for (int i= 0; i < length; i++){ char c= s.charAt(i); sb.append(PropertiesFileEscapes.escape(c)); } if(!escapeCommentCharsAndLeadingWhitespaces) return sb.toString(); return escapeLeadingWhiteSpaces(escapeCommentChars(sb.toString())); } private static String escapeCommentChars(String string) { StringBuffer sb = new StringBuffer(string.length() + 5); for (int i = 0; i < string.length(); i++) { char c = string.charAt(i); switch (c) { case '!': sb.append("\\!"); //$NON-NLS-1$ break; case '#': sb.append("\\#"); //$NON-NLS-1$ break; default: sb.append(c); } } return sb.toString(); } private static String escapeLeadingWhiteSpaces(String str) { int firstNonWhiteSpace= findFirstNonWhiteSpace(str); StringBuffer buf= new StringBuffer(firstNonWhiteSpace); for (int i = 0; i < firstNonWhiteSpace; i++) { buf.append('\\'); buf.append(str.charAt(i)); } buf.append(str.substring(firstNonWhiteSpace)); return buf.toString(); } /** * @param s the string to inspect * @return the first non whitespace character, the length if only whitespace characters */ private static int findFirstNonWhiteSpace(String s) { for (int i = 0; i < s.length(); i++) { if (!Character.isWhitespace(s.charAt(i))) return i; } return s.length(); } private static class KeyValuePairModell extends KeyValuePair { int fOffset; int fLeadingWhiteSpaces; public KeyValuePairModell(String key, String value, int offset, int leadingWhiteSpaces) { super(key, value); fOffset = offset; fLeadingWhiteSpaces = leadingWhiteSpaces; } public KeyValuePairModell(KeyValuePair keyValuePair) { super(keyValuePair.fKey, keyValuePair.fValue); } public int getLength() { return fKey.length() + 1 + fValue.length(); } private String getKeyValueText() { return fKey + '=' + fValue; } } /** * anchor element for a list of KeyValuePairs. (it is greater than every * other KeyValuePair) */ private static class LastKeyValuePair extends KeyValuePairModell { private boolean fNeedsNewLine; public LastKeyValuePair(int offset, boolean needsNewLine) { super("zzzzzzz", "key", offset, 0); //$NON-NLS-1$ //$NON-NLS-2$ fNeedsNewLine= needsNewLine; } public boolean needsNewLine() { return fNeedsNewLine; } public void resetNeedsNewLine() { fNeedsNewLine= false; } } }